<?php
/**
 * @author HelloWorld
 * 2014-9-23
 * UTF-8
 */
class DB_Adapter_Pdo extends DB_Abstract implements DB_Interface
{
    private $pdo = null;
    private $stmt = null;
    private $prep_sql = array();
    private $prep_data = array();
    private $encrypt_field = [];
    private static $pdo_arr = array();

    /**
     * pdo值类型.
     *
     * @var array
     */
    private $pdo_type = array(
            'integer' => PDO::PARAM_INT,
            'int' => PDO::PARAM_INT,
            'boolean' => PDO::PARAM_BOOL,
            'bool' => PDO::PARAM_BOOL,
            'string' => PDO::PARAM_STR,
            'null' => PDO::PARAM_NULL,
            'object' => PDO::PARAM_LOB,
            'float' => PDO::PARAM_STR,
            'double' => PDO::PARAM_STR,
    );

    public function __construct($db_config, $db_tag = '')
    {
        $this->_init($db_config, $db_tag);
    }

    /**
     * 获得链接.
     *
     * @param string $isReader
     *                         是否使用从库
     */
    public function getConn($isReader = true)
    {
        $this->pdo = $this->_connect($isReader);

        return $this;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::setData()
     */
    public function setData($sql, $data)
    {
        if (!is_array($data)) {
            throw new Exception('The data must be array.');
        }
        if (!is_string($sql)) {
            throw new Exception('The sql must be string.');
        }
        $this->prep_sql = $sql;
        $this->prep_data = $data;
        $this->encrypt_field = Lib_EncryptField::$encrypt_field;
        return $this;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::excute()
     */
    public function excute()
    {
        $this->sqlencrypt();
        $this->dataencrypt();
        $this->exec();

        return true;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::beginTransaction()
     */
    public function beginTransaction()
    {
        //开启事务之前清除参与执行sql
        if (!empty($this->prep_sql)) {
            $this->prep_sql = '';
        }
        if (!$this->pdo->inTransaction()) {
            $this->pdo->beginTransaction();
        }

        return $this;
    }

    /**
     * (non-PHPdoc).
     *
     * @see TyqDB_Interface::rollback()
     */
    public function rollback()
    {
        // TODO Auto-generated method stub
        $res = false;

        if ($this->pdo->inTransaction()) {
            try {
                $res = $this->pdo->rollBack();
            } catch (PDOException $e) {
                $this->_close();
                Func_Log::write('system_sql', 'transaction', $e->getMessage());
            }
        }
        $this->_close();

        return $res;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::commit()
     */
    public function commit()
    {
        $res = null;
        if ($this->pdo->inTransaction()) {
            try {
                $res = $this->pdo->commit();
            } catch (PDOException $e) {
                // 出问题回滚 写入日志
                $this->pdo->rollBack();
                $this->_close();
                Func_Log::write('system_sql', 'transaction', $e->getMessage());

                return false;
            }
            $this->_close();

            return $res;
        } else {
            $this->_close();
            throw new Exception('There is no active transaction.');
        }
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::fetchAll()
     */
    public function fetchAll()
    {
        $this->checkStatus();
        $this->excute();
        $res = $this->stmt->fetchAll(PDO::FETCH_ASSOC);
        $this->_close();

        return  $this->resDecrypt($res);
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::fetchColumn()
     */
    public function fetchColumn($column_num)
    {
        $this->checkStatus();
        $this->excute();
        $res = $this->stmt->fetchColumn($column_num);
        $this->_close();

        return $res;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::fetchOne()
     */
    public function fetchOne()
    {
        $this->checkStatus();
        $this->excute();
        $res = $this->stmt->fetch(PDO::FETCH_ASSOC);
        $this->_close();

        return $this->resDecrypt($res);
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::lastInsertId()
     */
    public function lastInsertId()
    {
        $this->checkStatus();
        $this->excute();
        $res = $this->pdo->lastInsertId();
        $this->_close();

        return $res;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Interface::affectedCount()
     */
    public function affectedCount()
    {
        $this->checkStatus();
        $this->excute();
        $res = $this->stmt->rowCount();
        $this->_close();

        return $res;
    }

    /**
     * 校验链接.
     *
     * @throws Exception
     */
    private function checkStatus()
    {
        if (empty($this->pdo)) {
            throw new Exception('pdo is null.');
        }
        //mysql 重连 #TODO 默认为主库重连
        $ping = $this->pdo_ping($this->pdo);
        if (!$ping) {
            Func_Log::write('reconncet', 'connect', var_export($this->prep_sql, 1));
            $this->pdo = $this->_connect(false);
        }
    }

    /**
     * 执行语句
     * 调整为单条执行，执行完毕后清理 sql.
     *
     * @throws Exception
     */
    private function exec()
    {
        // 校验数据是否正确
        if (!is_string($this->prep_sql) || !is_array($this->prep_data)) {
            throw new Exception('Type is error.');
        }
        $this->stmt = $this->pdo->prepare($this->prep_sql, array(
                PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY,
        ));
        $data_keys = array_keys($this->prep_data);
        $data_values = array_values($this->prep_data);
        for ($j = 0; $j < count($this->prep_data); ++$j ) {
            $type = $this->pdo_type[gettype($data_values[$j])];
            if ($type == PDO::PARAM_INT) {
                $data_values[$j] = intval($data_values[$j]);
            }
            $this->stmt->bindParam($data_keys[$j], $data_values[$j], $type);
        }
        $this->stmt->execute();
        $this->prep_sql = '';
        $this->prep_data = array();
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Abstract::_connect()
     */
    protected function _connect($isReader = true)
    {
        $config = $isReader ? $this->_reader : $this->_writer;
        try {
            $hash = md5($config['host'].$config['database'].$config['port'].$config['user']);
            if (!isset(self::$pdo_arr[$hash]) || !$this->pdo_ping(self::$pdo_arr[$hash])) {
                $pdo = new PDO("mysql:host={$config['host']};dbname={$config['database']};port={$config['port']};charset={$config['charset']}", $config['user'], $config['password'], array(
                        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                        PDO::ATTR_TIMEOUT => 2,
                ));
                self::$pdo_arr[$hash] = $pdo;
            }
        } catch (PDOException $e) {
            $msg = $e->getMessage();
            Func_Log::write('system_sql', 'connect', $msg);
        }

        return self::$pdo_arr[$hash];
    }

    /**
     * 检查连接是否可用.
     *
     * @param Link $dbconn 数据库连接
     *
     * @return bool
     */
    public function pdo_ping($dbconn)
    {
        try {
            $dbconn->getAttribute(PDO::ATTR_SERVER_INFO);
        } catch (ErrorException $e) {
            if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
                return false;
            }
        } catch (PDOException $e) {
            if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
                return false;
            }
        } catch (Exception $e) {
            if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) {
                return false;
            }
        }

        return true;
    }

    /**
     * (non-PHPdoc).
     *
     * @see DB_Abstract::_close()
     */
    protected function _close()
    {
        if (!empty($this->pdo) && !($this->pdo->inTransaction())) {
            $this->stmt = null;
            //$this->pdo = null;
            $this->prep_sql = '';
            $this->prep_data = array();
        }
    }

    //数据加密

    public function dataencrypt(){
        $data_keys = array_keys($this->prep_data);
        //获取需要加密的字段
        $field = $this->encrypt_field;
        $encrypt_field = array_filter($data_keys, function($item) use ($field) {
            // 检查是否包含$a中的任意值，并且符合格式要求
            foreach ($field as $search) {
                // 查找搜索词出现的位置
                $pos = strpos($item, $search);

                // 检查是否找到且位置正确（在冒号后第一个位置）
                if ($pos !== false && $pos === 1) {
                    // 获取搜索词后面的字符
                    $next_char_pos = $pos + strlen($search);
                    $next_char = substr($item, $next_char_pos, 1);

                    // 验证格式：后面必须是数字或字符串结束
                    if (!ctype_alpha($next_char) || $next_char === '') {
                        return true;
                    }
                }
            }
            return false;
        });
        if($encrypt_field){
            //数据加密
            foreach ($encrypt_field as $val){
                if($this->prep_data[$val]){
                    $this->prep_data[$val] =  Lib_infoAes::getInstance()->aesEncrypt($this->prep_data[$val]);

                }
            }
        }
    }
        //SQL加密
    public function sqlencrypt(){
        //含有需要加密字段的个数
        if (stristr($this->prep_sql,"WHERE")){
            str_replace($this->encrypt_field, '', stristr($this->prep_sql,"WHERE") ,$count);
            if($count){
                foreach ($this->encrypt_field as $val){
                    if(strstr(stristr($this->prep_sql,"WHERE"), $val)){
                        switch (true){
                            //可能存在int类型判断
                            case strstr($val,'mobile')://手机号类型
                                $zzgs = '/ ' .$val . '\s*=\s*(\d{11})\s*/';
                                preg_match($zzgs, $this->prep_sql , $matches);
                                if(empty($matches)){
                                    $zzgs = '/ ' .$val . '\s*=\s*[\'"](\d{11})[\'"]\s*/';
                                    preg_match($zzgs, $this->prep_sql , $matches);
                                    if(!empty($matches)){
                                        $temp = Lib_infoAes::getInstance()->aesEncrypt($matches[1]);
                                        $this->prep_sql = str_replace($matches[1],$temp,$this->prep_sql);
                                    }
                                }else{
                                    $temp = Lib_infoAes::getInstance()->aesEncrypt($matches[1]);
                                    $this->prep_sql = str_replace($matches[1],'"'.$temp.'"',$this->prep_sql);
                                }

                                break;
                            case strstr($val,'like') && $val == 'litigantName':
                                $this->prep_sql = preg_replace_callback(
                                    '/%([^%]+)%/',  // 匹配 %任意非%字符%
                                    function ($matches) {
                                        $temp = Lib_infoAes::getInstance()->aesEncrypt($matches[1]);
                                        return '%' . $temp . '%';  // 替换为 MD5 值，并保留 %
                                    },
                                    $this->prep_sql
                                );
                                break;
                            default: //其余字符串 直接匹配替换
                               // $zzgs = '/ ' .$val . '\s*=\s*[\'"](.*)[\'"]\s*/';
                                $zzgs = '/\b' . $val . '\s*=\s*[\'"]([^\'"]*)[\'"]\s*/';
                                preg_match($zzgs, $this->prep_sql , $matches);
                                if(!empty($matches)){
                                    $temp = Lib_infoAes::getInstance()->aesEncrypt($matches[1]);
                                    $this->prep_sql = str_replace($matches[1],$temp,$this->prep_sql);
                                }
                        }
                    }
                }
            }
//            echo $this->prep_sql."\n";
        }
    }
    //返回解密
    public function resDecrypt($res){
        if(empty($res)){
            return $res;
        }
        $encrypt_field = $this->encrypt_field;
        if (count($res) == count($res, 1)) {
            $encrypt_field = array_intersect($encrypt_field, array_keys($res));
            if($encrypt_field){
                foreach($encrypt_field as $key => $val){
                    $decryptData = Lib_infoAes::getInstance()->aesDecrypt($res[$val]);
                    $res[$val] = $decryptData ? $decryptData : $res[$val];
                }
            }

        }else{
            $encrypt_field = array_intersect($encrypt_field, array_keys($res[0]));
            if($encrypt_field){
                foreach($res as $key => $val){
                    foreach($encrypt_field as $v){
                        if(isset($val[$v])){
                            $decryptData = Lib_infoAes::getInstance()->aesDecrypt($val[$v]);
                            $res[$key][$v] = $decryptData ? $decryptData : $val[$v];
                        }
                    }
                }
            }
        }
        return $res;
    }

}
